from ultralytics import YOLO
from PIL import Image
import time
import torch

import subprocess
import onnxruntime as ort

import win32gui, win32ui, win32con
import numpy as np

import cv2
import joblib
import requests
import re
import math

# 导入键盘控制模块
import pyautogui

pyautogui.FAILSAFE = False
import pydirectinput

pydirectinput.PAUSE = 0.1  # 设置按键间隔


def is_cuda_available():
    try:
        result = subprocess.run(['nvidia-smi'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return result.returncode == 0
    except FileNotFoundError:
        return False


# 加载模型
model = YOLO('./model/best.pt', verbose=False)  # 请替换为实际的模型路径
# # 加载ONNX模型
# model = YOLO("./model/best.onnx", task='detect')  # 关键代码：加载模型时绑定设备

# 设置推理置信度阈值，例如设置为 0.5，低于该阈值的检测结果将被过滤
confidence_threshold = 0.90
# 选择设备，0 表示使用第一张 GPU 卡，'cpu' 表示使用 CPU
device = 0 if torch.cuda.is_available() else 'cpu'

model.to(device)

# print('读卡模式【==推理卡==】:', model.device.type)


def window_capture_win(hwnd):
    # 根据窗口句柄获取窗口的设备上下文DC（Divice Context）
    hwndDC = win32gui.GetWindowDC(hwnd)
    # 根据窗口的DC获取mfcDC
    mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    # mfcDC创建可兼容的DC
    saveDC = mfcDC.CreateCompatibleDC()
    # 创建bigmap准备保存图片
    saveBitMap = win32ui.CreateBitmap()
    w = 800
    h = 600
    # 为bitmap开辟空间
    saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
    # 高度saveDC，将截图保存到saveBitmap中
    saveDC.SelectObject(saveBitMap)
    # 截取从左上角（0，0）长宽为（w，h）的图片
    saveDC.BitBlt((0, 0), (w, h), mfcDC, (0, 0), win32con.SRCCOPY)
    signed_ints_array = saveBitMap.GetBitmapBits(True)
    img = np.frombuffer(signed_ints_array, dtype="uint8")
    img.shape = (h, w, 4)
    win32gui.DeleteObject(saveBitMap.GetHandle())
    mfcDC.DeleteDC()
    saveDC.DeleteDC()
    return img


def window_capture_win_qy(hwnd, x=0, y=0, w=800, h=600):
    # 获取窗口设备上下文（DC）
    hwndDC = win32gui.GetWindowDC(hwnd)
    mfcDC = win32ui.CreateDCFromHandle(hwndDC)
    saveDC = mfcDC.CreateCompatibleDC()

    # 创建位图对象并分配空间
    saveBitMap = win32ui.CreateBitmap()
    saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)

    # 截图操作
    saveDC.SelectObject(saveBitMap)
    saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY)

    # 转换为 NumPy 数组
    signed_ints_array = saveBitMap.GetBitmapBits(True)
    img = np.frombuffer(signed_ints_array, dtype='uint8')
    img.shape = (h, w, 4)  # 注意是4通道（BGRA）

    # 清理资源
    win32gui.DeleteObject(saveBitMap.GetHandle())
    mfcDC.DeleteDC()
    saveDC.DeleteDC()
    win32gui.ReleaseDC(hwnd, hwndDC)
    return img[:, :, :3]


# 定义类别名称映射
class_mapping = {
    0: "hero",
    1: "monster",
    2: "goods",
    3: "boos",
    4: "door",
    5: "brand",
    6: "again"
}

# 定义映射规则 数字识别
mapping = {
    '62226': '0',
    '281': '1',
    '43333': '2',
    '22335': '3',
    '22482': '4',
    '63334': '5',
    '53334': '6',
    '1532': '7',
    '44335': '8',
    '43335': '9',
    '1': '.'
}

"""
    识别移动速度
"""


def calculateMovingSpeed(img):
    # 将图像转换为灰度图
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 使用 np.where 计算每列中大于阈值的像素数量
    column_counts = np.sum(img > 120, axis=0)
    current_sublist = []
    result = []
    for item in column_counts:
        if item == 0:
            if current_sublist:
                sublist_str = ''.join(map(str, current_sublist))
                if sublist_str in mapping:
                    result.append(mapping[sublist_str])
                current_sublist = []
        else:
            current_sublist.append(item)
    if current_sublist:
        sublist_str = ''.join(map(str, current_sublist))
        if sublist_str in mapping:
            result.append(mapping[sublist_str])
    return ''.join(result)


"""
    疲劳值检测推算
"""


def find_pl(image):
    img_bg = Image.fromarray(image)
    img = img_bg.convert('L')
    box1 = (4, 2, 7, 7)
    box2 = (9, 2, 12, 7)
    box3 = (14, 2, 17, 7)
    x = [img.crop(box1).getdata(), img.crop(box2).getdata(), img.crop(box3).getdata()]
    lr = joblib.load(r'./model/pl.pkl')
    y = lr.predict(x)
    filtered = [str(n) for n in y if n >= 0]
    return int("".join(filtered)) if filtered else -1  # 为空时返回默认值0


def call_api_direct(img):
    # 将图片编码转为二进制数据
    _, img_encoded = cv2.imencode('.jpg', img)
    # 发送请求
    resource = requests.post('http://120.26.59.203:8080/api/crop_image',
                             data=img_encoded.tobytes(),
                             headers={'Content-Type': 'application/octet-stream'})
    return resource.text


def split_level_and_name(api_response):
    # 定义正则表达式模式，匹配数字+级+剩余部分
    pattern = r'^(\d+)级(.+)$'
    match = re.match(pattern, api_response)

    if match:
        level = int(match.group(1))  # 转换等级为整数
        name = match.group(2)
        return level, name
    else:
        # 处理无法匹配的情况
        print(f"无法解析API响应: {api_response}")
        return None, None


# 检查是否存在特定类别的目标
def check_target_exists(data_with_ids, target_id):
    for item_id, _ in data_with_ids:
        if item_id == target_id:
            return True
    return False


# 直接使用类别ID处理数据
def process_detection_results(results):
    # 直接取 y的最大值
    data = [
        (int(box.cls.item()),  # 类别ID
         [(box.xyxy.tolist()[0][0] + box.xyxy.tolist()[0][2]) / 2,  # 中心点x坐标
          (box.xyxy.tolist()[0][3])],  # 中心点y坐标
         )
        for r in results for box in r.boxes
    ]
    return data


# 获取特定类别的详细信息
def get_target_info(data_with_ids, target_id, class_mapping):
    for item_id, info in data_with_ids:
        if item_id == target_id:
            return {
                "id": item_id,
                "name": class_mapping.get(item_id, "未知类别"),
                "info": info
            }
    return None


# 使用正则表达式
def extract_with_regex(text):
    pattern = r'\[(.*?)\]'
    match = re.search(pattern, text)
    if match:
        return match.group(1)
    return ""


# 模板匹配法
def template_picture(img, template_path: str, threshold: float, flags: int):
    try:
        template = cv2.imread(template_path, flags)
        if template is None:
            return False, None
        # 0 色彩以灰度加载   1 以色彩加载
        if flags == 0:
            img_proc = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        else:
            img_proc = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)

        res = cv2.matchTemplate(img_proc, template, cv2.TM_SQDIFF_NORMED)
        min_val, _, min_loc, _ = cv2.minMaxLoc(res)

        if min_val > 1 - threshold:
            return False, None

        h, w = template.shape[:2]
        x, y = min_loc
        return True, (x + w / 2, y + h / 2, x, y, x + w, y + h)

    except cv2.error:
        return False, None


def match_template_with_loaded_template(main_img, template_path, threshold=0.01):
    """
    使用模板文件，在主图（图像数据）中查找子图位置

    参数:
        main_img (np.ndarray): 主图图像数据（BGR）
        template_path (str): 模板图文件路径
        threshold (float): 匹配误差阈值（越小越严格）

    返回:
        dict: {
            'position': (x, y) 或 None,
            'score': float,
            'time_ms': float,
            'found': True / False
        }
    """
    if main_img is None:
        raise ValueError("主图不能为空")

    template_img = cv2.imread(template_path)
    if template_img is None:
        raise FileNotFoundError(f"无法读取模板图：{template_path}")

    start = time.time()

    result = cv2.matchTemplate(main_img, template_img, cv2.TM_SQDIFF_NORMED)
    min_val, _, min_loc, _ = cv2.minMaxLoc(result)

    elapsed = (time.time() - start) * 1000  # 毫秒

    return {
        'position': min_loc if min_val < threshold else None,
        'score': float(min_val),
        'time_ms': float(elapsed),
        'found': min_val < threshold
    }


def match_templates_with_loaded_templates(main_img, template_paths, threshold=0.01):
    """
    使用多个模板文件，在主图中查找所有模板的位置

    参数:
        main_img (np.ndarray): 主图图像数据（BGR）
        template_paths (list of str): 模板图文件路径列表
        threshold (float): 匹配误差阈值（越小越严格）

    返回:
        list of dict: 每个模板的匹配结果列表
    """
    if main_img is None:
        raise ValueError("主图不能为空")

    results = []

    for path in template_paths:
        template_img = cv2.imread(path)
        if template_img is None:
            results.append({
                'template': path,
                'error': f"无法读取模板图：{path}"
            })
            continue
        if path in ["./model/n.png", "./model/b.png"]:
            threshold = 0.01
        start = time.time()
        result = cv2.matchTemplate(main_img, template_img, cv2.TM_SQDIFF_NORMED)
        min_val, _, min_loc, _ = cv2.minMaxLoc(result)
        elapsed = (time.time() - start) * 1000

        results.append({
            'template': path,
            'position': min_loc if min_val < threshold else None,
            'score': float(min_val),
            'time_ms': float(elapsed),
            'found': min_val < threshold
        })

    return results


# 计算两点之间的高度差
def calculate_height_difference(point1, point2):
    """计算两点之间的高度差（y坐标的差值）"""
    return abs(point1[1] - point2[1])


# 获取图片信息
def get_win_image_info(hwnd):
    image = window_capture_win(hwnd)
    # 由于 OpenCV 默认使用 BGR 颜色空间，而截图可能是 BGRA，这里去掉透明度通道
    image = image[:, :, :3]
    # stat_time = time.time()
    # 使用 YOLO 模型进行目标检测
    results = model(image, conf=confidence_threshold, verbose=False)
    # print(f'推理结果：{results}')
    # end_time = time.time()
    # print('推理时长:', (end_time - stat_time), '秒')

    # 使用列表推导式将生成器结果转换为列表
    return process_detection_results(results), image

def get_image_info(image):
    # stat_time = time.time()
    # 使用 YOLO 模型进行目标检测
    results = model(image, conf=confidence_threshold,verbose=False)
    # end_time = time.time()
    # print('推理时长:', (end_time - stat_time), '秒')
    data = [
        (int(box.cls.item()),  # 类别ID
         [(box.xyxy.tolist()[0][0] + box.xyxy.tolist()[0][2]) / 2,  # 中心点x坐标
          (box.xyxy.tolist()[0][3])],  # 中心点y坐标
         )
        for r in results for box in r.boxes
    ]
    return data


def get_direction_key_funcs(direction):
    """
    返回对应方向的 keyDown 和 keyUp 方法，用于长按和释放方向键。
    支持方向键：'up', 'down', 'left', 'right'
    """
    key_map = {
        'up': 'up',
        'down': 'down',
        'left': 'left',
        'right': 'right'
    }
    key = key_map.get(direction)

    def press():
        pydirectinput.press(key)
        pydirectinput.keyDown(key)

    def release(): pydirectinput.keyUp(key)

    return press, release


def move_until_door_detected(hwnd, direction, press_key_func, release_key_func, detection_interval=0.1):
    """
    持续按住移动键，直到在指定方向检测到门。

    参数:
        hwnd: 窗口句柄，用于截图
        direction: 'up' | 'down' | 'left' | 'right'
        press_key_func: 按住方向键的函数（如按住W键）
        release_key_func: 弹起方向键的函数
        detection_interval: 每次检测的间隔时间（秒）

    返回:
        (door_pos, current_player_pos): 检测到的门坐标和当前人物坐标（用于后续逻辑）
    """

    # 预检查：在移动前先检查门是否已经在指定方向可见
    detections, _ = get_win_image_info(hwnd)
    player_pos = next(
        ((x, y) for class_id, (x, y) in detections if class_id == 0),
        None  # 默认值（如果没找到玩家）
    )

    if player_pos is None:
        print("[!] 未找到玩家位置，无法判断门的方向")
        return None, None

    # 检查是否已经有门在指定方向
    for class_id, [x, y] in detections:
        label = class_mapping.get(class_id)
        if label == 'door':
            door_pos = (x, y)
            if is_door_in_direction(player_pos, door_pos, direction):
                print(f"[✓] 无需移动，门已在 {direction} 方向可见：{door_pos}")
                return door_pos, player_pos

    # 1. 按住方向键（如果预检查未发现门）
    press_key_func()
    print(f"[→] 开始向 {direction} 移动，寻找门")

    try:
        while True:
            detections, _ = get_win_image_info(hwnd)
            # 更新玩家位置
            player_pos = next(
                ((x, y) for class_id, (x, y) in detections if class_id == 0),
                player_pos  # 如果没找到新位置，使用旧位置
            )

            for class_id, [x, y] in detections:
                label = class_mapping.get(class_id)
                if label == 'door':
                    door_pos = (x, y)
                    if is_door_in_direction(player_pos, door_pos, direction):
                        print(f"[✓] 检测到门：{door_pos} 在 {direction} 方向")
                        release_key_func()
                        return door_pos, player_pos
            time.sleep(detection_interval)
    finally:
        release_key_func()  # 防止异常导致键未释放


def is_door_in_direction(player_pos, door_pos, direction):
    px, py = player_pos
    dx, dy = door_pos
    if direction == 'up':
        return dy < py
    elif direction == 'down':
        return dy > py
    elif direction == 'left':
        return dx < px
    elif direction == 'right':
        return dx > px
    return False


def extract_player_pos(datas):
    for class_id, (x, y) in datas:
        if class_id == 0:  # 0 对应 hero
            return x, y
    return None  # 如果没有找到人物


def extract_monster_pos(datas):
    # print(datas)
    monster_points, direction = find_closest_point(datas)
    # monster_points = [(x, y) for class_id, (x, y) in datas if class_id == 1]

    if not monster_points:  # 如果没有找到怪物
        return None, None
    # # 按距离原点(0,0)的欧氏距离排序
    # sorted_monsters = sorted(monster_points, key=lambda p: (p[0] ** 2 + p[1] ** 2) ** 0.5)

    return monster_points, direction  # 返回距离原点最近的怪物位置


# 计算角色到怪物距离的最小位移【坐标点】
def find_closest_point(data):
    # 提取标签0的坐标（假设只有一个标签0的点）
    point_0 = next((point for label, point in data if label == 0), None)
    if point_0 is None:
        return None, None

    # 提取所有标签1的坐标
    points_1 = [point for label, point in data if label == 1]
    if not points_1:
        return None, None

    # 计算每个标签1的点到标签0的点的距离，并找出最短距离对应的点
    closest_point = None
    min_distance = float('inf')

    for point in points_1:
        # 计算欧氏距离
        distance = math.sqrt(
            (point[0] - point_0[0]) ** 2 +
            (point[1] - point_0[1]) ** 2
        )
        if distance < min_distance:
            min_distance = distance
            closest_point = point
    if point_0[0] - closest_point[0] > 0:
        return (closest_point[0] + 50, closest_point[1]), 'left'
    return (closest_point[1] - 50, closest_point[1]), 'right'


def extract_goods_pos(datas):
    for class_id, (x, y) in datas:
        if class_id == 2:  # 0 对应 goods
            return x, y
    return None  # 如果没有找到物品


def extract_door_pos(datas):
    for class_id, (x, y) in datas:
        if class_id == 4:  # 0 对应 door
            return x, y
    return None  # 如果没有找到门坐标


# 查找翻拍
def extract_brand_pos(datas):
    print(f"==翻牌==={datas}")
    for class_id, _ in datas:
        if class_id == 5:  # 0 对应 brand
            return True
    time.sleep(1.5)
    return None  # 如果没有找到牌坐标


def extract_again_pos(datas):
    print(f"==挑战==={datas}")
    for class_id, _ in datas:
        if class_id == 6:  # 0 对应 again
            return True
    time.sleep(1.5)
    return None  # 如果没有找到再次挑战坐标


def extract_door_pos_value(datas, sort_by_x=True, select_max=False):
    """
    从数据集提取类别为4的坐标点，并根据指定条件选择极值点

    参数:
        datas: 数据集，格式为[(class_id, [x, y]), ...]
        sort_by_x: 是否按x坐标排序（默认为True）
        select_max: 是否选择最大值（默认为False，选择最小值）

    返回:
        符合条件的坐标(x, y)，若未找到则返回None
    """
    print(f'输出数据集合===={datas}')

    # 筛选类别为4的坐标点
    door_points = [(x, y) for class_id, (x, y) in datas if class_id == 4]

    if not door_points:
        return None

    # 获取排序键（x或y）
    sort_key = lambda p: p[0] if sort_by_x else p[1]

    # 使用min/max函数直接获取极值点（避免排序开销）
    return max(door_points, key=sort_key) if select_max else min(door_points, key=sort_key)


def move_and_scan_for_door(hwnd, direction, press_key_func, release_key_func, detection_interval=0.1, shake_steps=2,
                           shake_interval=0.2):
    """
    先向指定方向（up/down）移动一段距离，再进行左右摇晃以检测是否能看到门。

    参数:
        hwnd: 窗口句柄
        direction: 'up' or 'down'
        press_key_func: 按下方向键的函数（如 pydirectinput.keyDown）
        release_key_func: 释放方向键的函数（如 pydirectinput.keyUp）
        detection_interval: 检测门间隔
        shake_steps: 摇晃步数（左右探测次数）
        shake_interval: 摇晃间隔时间

    返回:
        (door_pos, player_pos): 找到门的位置及玩家当前位置
    """
    # 先上下移动一段时间（比如 2 秒）
    move_duration = 2.0
    print(f"[↑↓] 向{direction}移动 {move_duration} 秒...")
    press_key_func()
    time.sleep(move_duration)
    release_key_func()

    # 检测玩家位置
    data, _ = get_win_image_info(hwnd)
    player_pos = extract_player_pos(data)
    if player_pos is None:
        print("[!] 无法定位玩家")
        return None, None

    # 执行左右摇晃扫描
    print("[~] 上下移动完成，开始左右微移动扫描门...")
    for i in range(shake_steps):
        for dir_key in ['left', 'right']:
            press, release = get_direction_key_funcs(dir_key)
            press()
            time.sleep(shake_interval)
            release()

            data, _ = get_win_image_info(hwnd)
            door = next(((x, y) for cid, (x, y) in data if class_mapping.get(cid) == 'door'), None)
            player_pos = extract_player_pos(data)
            if door and is_door_in_direction(player_pos, door, direction):
                print(f"[✓] 找到门 {door} 在 {direction} 方向（第 {i + 1} 次左右探测）")
                return door, player_pos
    print("[x] 未找到门")
    return None, player_pos


def is_player_stuck(current_pos, last_pos, stuck_threshold=1):
    if not current_pos or not last_pos:
        return False
    dx = abs(current_pos[0] - last_pos[0])
    dy = abs(current_pos[1] - last_pos[1])
    return dx < stuck_threshold and dy < stuck_threshold


def move_with_obstacle_handling(hwnd, direction, press_key_func, release_key_func, timeout=3.0, check_interval=0.3):
    print(f"[→] 尝试向 {direction} 移动最多 {timeout} 秒，检测障碍物...")
    start_time = time.time()
    press_key_func()
    last_pos = None
    stuck_start = None
    current_pos = None

    try:
        while time.time() - start_time < timeout:
            detections, _ = get_win_image_info(hwnd)
            current_pos = extract_player_pos(detections)
            if current_pos:
                if last_pos and is_player_stuck(current_pos, last_pos):
                    if stuck_start is None:
                        stuck_start = time.time()
                    elif time.time() - stuck_start > 1.0:
                        print("[!] 人物长时间未移动，可能被障碍物卡住")
                        return False, current_pos
                else:
                    stuck_start = None
                last_pos = current_pos
            time.sleep(check_interval)
        print("[✓] 移动完成，无明显障碍")
        return True, current_pos
    finally:
        release_key_func()


def get_mini_map(hwnd):
    # 先截图整个窗口
    image = window_capture_win_qy(hwnd, 0, 0, 800, 600)
    # 再从整个窗口分离出小地图信息
    box = (670, 56, 123, 54)
    mini_map_img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]

    # cv2.imwrite('mini_map.png', mini_map_img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    templates = ["./model/i.png", "./model/n.png", "./model/b.png"]
    minimaps = match_templates_with_loaded_templates(mini_map_img, templates, threshold=0.3199)
    # for m in minimaps:
    #     print(
    #         f"模板：{m['template']}, 匹配：{m['found']}, 位置：{m['position']}, 分数：{m['score']:.4f}, 耗时：{m['time_ms']:.2f}ms")
    return minimaps, image


def get_mini_map_img(image):
    box = (670, 56, 123, 54)
    img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    templates = ["./model/i.png"]
    minimaps = match_templates_with_loaded_templates(img, templates, threshold=0.3199)
    i_room = next((d for d in minimaps if d['template'].endswith('i.png') and d['found']), None)
    if not i_room:
        return 'unknown', None, None
    return int(i_room['position'][0]) // 18, int(i_room['position'][1]) // 18


def get_mini_map_type(mini_map_img):
    templates = ["./model/islands.png", "./model/engine.png", "./model/forest.png"]
    minimaps = match_templates_with_loaded_templates(mini_map_img, templates, threshold=0.01)
    # for m in minimaps:
    #     print(
    #         f"模板：{m['template']}, 匹配：{m['found']}, 位置：{m['position']}, 分数：{m['score']:.4f}, 耗时：{m['time_ms']:.2f}ms")
    return minimaps


def get_green_block(hwnd):
    main_img = window_capture_win_qy(hwnd, 0, 322, 800, 600)
    templates = ["./model/green_block.png"]
    greenBlocks = match_templates_with_loaded_templates(main_img, templates, threshold=0.59)
    for m in greenBlocks:
        print(
            f"模板：{m['template']}, 匹配：{m['found']}, 位置：{m['position']}, 分数：{m['score']:.4f}, 耗时：{m['time_ms']:.2f}ms")
    return greenBlocks


def get_next_room_direction(data):
    i_room = next((d for d in data if d['template'].endswith('i.png') and d['found']), None)
    if not i_room:
        return 'unknown', None, None
    ix, iy = i_room['position'][0] // 18, i_room['position'][1] // 18
    n_room = next((d for d in data if d['template'].endswith('n.png') and d['found']), None)
    if not n_room:
        return 'unknown', (ix, iy), None

    nx, ny = n_room['position'][0] // 18, n_room['position'][1] // 18

    if nx == ix + 1 and ny == iy:
        return 'right', (ix, iy), (nx, ny)
    if nx == ix - 1 and ny == iy:
        return 'left', (ix, iy), (nx, ny)
    if nx == ix and ny == iy - 1:
        return 'up', (ix, iy), (nx, ny)
    if nx == ix and ny == iy + 1:
        return 'down', (ix, iy), (nx, ny)
    return 'unknown', None, None


def mouse_to_click(win_left, win_top, target_x, target_y):
    pydirectinput.moveTo(win_left + int(target_x), win_top + int(target_y))
    pydirectinput.mouseDown(None, None, "left", None, None, None, True)
    time.sleep(0.5)
    pydirectinput.mouseUp(None, None, "left", None, None, None, True)


if __name__ == '__main__':
    hwnd = win32gui.FindWindow("地下城与勇士", "地下城与勇士：创新世纪")
    data_with_ids, image = get_win_image_info(hwnd)
    # while True:
    #     data_with_ids, image = get_win_image_info(hwnd)
    #     print(f"检测结果：{data_with_ids}")
    #     time.sleep(1.5)
    # data = get_mini_map_img(image)
    # print(data_with_ids)
    # cv2.imshow('Window Screenshot', img)
    # cv2.waitKey(0)
    # 凌晨十二点关闭窗口
    # box = (374, 437, 52, 20)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    # cv2.imwrite('close_win.jpg', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # 赛丽亚
    # box = (427, 175+6, 37, 14)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    # # # 保存图片
    # cv2.imwrite('sly.jpg', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 模板图从文件读取
    # result = match_template_with_loaded_template(image, "green_block.png")
    # if result['found']:
    #     print(f"✅ 找到绿色方块：{result['position']}，误差：{result['score']:.5f}，耗时：{result['time_ms']:.2f}ms")
    # else:
    #     print(f"❌ 未找到，误差：{result['score']:.5f}")

    # hero_info = get_target_info(data_with_ids, 0, class_mapping)
    # result = match_template_with_loaded_template(image, "1.png")
    # print(result)
    # if result['found']:
    #     print(f"✅ 找到金色方块：{result['position']}，误差：{result['score']:.5f}，耗时：{result['time_ms']:.2f}ms")
    #     print(f"⏳ 5秒后开始移动，请切换到游戏窗口 称号位置：{hero_info} 金币位置：{result['position']}")
    #     time.sleep(5)
    #     move_towards_target(hero_info["info"], result['position'], rise=148, speed=107.0)
    # else:
    #     print(f"❌ 未找到，误差：{result['score']:.5f}")

    # 目标类别ID
    # cattle_id = 3  # "cattle"的ID
    #
    # # 赛利亚房间 推理身高
    # box = (427, 175, 37, 14)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # # cv2.imwrite('sly1.jpg', img)
    # stu, _ = template_picture(img, './sly.jpg', 0.95, 1)
    # print(f"是否赛利亚房间{stu}")
    # if stu:
    #     # 目标坐标点
    #     target_point = (450, 410)
    #     hero_id = 0  # "hero" 的ID
    #     hero_info = get_target_info(data_with_ids, hero_id, class_mapping)
    #     if hero_info:
    #         print(f"hero详细信息: {hero_info}")
    #         # 从英雄信息中提取坐标
    #         hero_coords = hero_info["info"][:2]  # 获取前两个值（x和y坐标）
    #         # 计算高度差
    #         height_difference = calculate_height_difference(hero_coords, target_point)
    #         # 输出结果
    #         print(f"目标点: ({target_point[0]}, {target_point[1]})")
    #         print(f"高度差: {round(height_difference)} 像素")  # 140
    # 检查是否存在cattle
    # if check_target_exists(data_with_ids, cattle_id):
    #     print("存在cattle(类别ID=3)，默认在副本中")
    #     # 获取cattle的详细信息
    #     cattle_info = get_target_info(data_with_ids, cattle_id, class_mapping)
    #     if cattle_info:
    #         print(f"cattle详细信息: {cattle_info}")
    #     last_pos = None
    #     tracker = RoomTracker()
    #     while True:
    #         data_with_ids, img = get_win_image_info(hwnd)
    #         print(data_with_ids)
    #         # 调用函数 检测地图方向
    #         bs_map = tracker.process(data_with_ids)
    #         print(bs_map)
    #         time.sleep(0.5)
    # box = (778, 92, 5, 12)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # # 副本小地图检测
    # get_mini_map(hwnd)
    # # 副本绿色块状检测
    # get_green_block(hwnd)
    #
    # player_pos = get_target_info(data_with_ids, 0, class_mapping)
    # door_pos = get_target_info(data_with_ids, 4, class_mapping)
    # print(f"检测称号坐标：{player_pos}  检测出门的坐标：{door_pos}")

    # 下 317   上237  右 600  左200
    #         if bs_map.get('status') == 'in_boss_room':
    #             break
    #         if bs_map.get('status') == 'at_boss_door':
    #             print(f"抵达boss门外 == 检测是否存在怪物，存在怪物先秒怪，不存在就过图")
    #             break
    #         if not v:
    #             print(f"未开启门====处理秒怪环节、然后在过图。")
    #             # 模板图从文件读取
    #             result = match_template_with_loaded_template(img, "green_block.png")
    #             if result['found']:
    #                 print(f"✅ 找到绿色方块：{result['position']}，误差：{result['score']:.5f}，耗时：{result['time_ms']:.2f}ms")
    #                 player_pos = extract_player_pos(data_with_ids)
    #                 # 检查 player_pos] 是否为 None
    #                 if player_pos is not None and result['position'] is not None:
    #                     move_towards_target(player_pos, result['position'], rise=180, speed=112.0)
    #                     # 释放技能
    #                     press_key = random_key_selector(img)
    #                     pydirectinput.press(press_key)
    #                 else:
    #                     print("未找到有效的目标位置，无法移动")
    #             else:
    #                 print(f"❌ 未找到，误差：{result['score']:.5f}")
    #         if v and bs_map.get('status') == 'normal':
    #             direction = bs_map.get('direction')
    #             print(f"下个房间: {direction}")
    #             if direction is None:
    #                 continue
    #             player_pos = extract_player_pos(data_with_ids)
    #             press_key, release_key = get_direction_key_funcs(direction)
    #             print(f"读取按键状态：{press_key}，{release_key}")
    #             # 如果方向为上下
    #             if direction in ['up', 'down']:
    #                 press, release = get_direction_key_funcs(direction)
    #                 door_pos, _ = move_and_scan_for_door(hwnd, direction, press, release,shake_interval=2)
    #             else:
    #                 press, release = get_direction_key_funcs(direction)
    #                 door_pos, _ = move_until_door_detected(hwnd, direction, press, release)
    #
    #             print(f"检测到门坐标: {door_pos}, 人物原始坐标: {player_pos}")
    #             if player_pos is not None and door_pos is not None:
    #                 move_towards_target(player_pos, door_pos, rise=220, speed=112.0)
    #             else:
    #                 print("未找到有效的目标位置，无法移动")
    #
    # else:
    #     print("不存在cattle")
    # 测边框
    win_left, win_top, _, _ = win32gui.GetWindowRect(hwnd)
    # box = (616, 195, 5, 9)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('close_triangle.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # box = (609, 179, 19, 89)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # stu, pos = template_picture(img, './close_triangle.png', 0.75, 1)
    # print(f"存在{stu}  点位{pos}")
    # mouse_to_click(win_left + 609, win_top + 179, pos[0], pos[1])
    # cv2.imwrite('close_triangle_show.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # 游戏开始
    # win_left, win_top, _, _ = win32gui.GetWindowRect(hwnd)
    # box = (372, 555, 68, 16)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('role_start.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 选择角色
    # win_left, win_top, _, _ = win32gui.GetWindowRect(hwnd)
    # box = (348, 518, 46, 12)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # stu, pos = template_picture(img, './model/role_tab.png', 0.75, 1)
    # if stu:
    #     # 选择角色 移动鼠标点击
    #     mouse_to_click(win_left + 348, win_top + 518, pos[0], pos[1])
    # box = (348, 518, 46, 12)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('role_tab.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 神秘商店
    # box = (159, 395, 65, 18)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # 妖气具现之型
    # box = (12, 449, 58, 28)
    # box = (12, 449, 28, 28) # 模板 神秘商店
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('shan_shan_mi.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # win_left, win_top, _, _ = win32gui.GetWindowRect(hwnd)
    # box = (159, 395, 65, 18)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # stu, _ = template_picture(img, './model/shop.png', 0.75, 1)
    # if stu:
    #     print('神秘')
    #     box = (12, 449, 58, 28)
    #     img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    #     stu, pos = template_picture(img, './model/yao_qi_ju_xian_zx.png', 0.75, 1)
    #     if stu:
    #         print(pos)
    #         mouse_to_click(win_left+12,win_top+449,pos[0],pos[1])
    #         pydirectinput.press('space')
    #     else:
    #         print('购买完成===')
    #         pydirectinput.press('esc')

    # 红矿村
    # box = (615, 28, 58, 16)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    # cv2.imwrite('HongKuAng_Village.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 妖气追踪
    # box = (0, 286, 267, 41)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    # cv2.imwrite('YaoQi_Track.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 返回诚镇
    # box = (701, 542, 95, 22)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]
    # cv2.imshow('Window Screenshot', img)
    # cv2.waitKey(0)
    # cv2.imwrite('return_town.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 是否出现翻拍标识 310 23 388 40
    # box = (315, 23, 74, 16)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # stu, _ = template_picture(img, './model/brand.png', 0.75, 1)

    # cv2.imwrite('brand.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 再次挑战
    # box = (632, 47, 80, 18)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # stu, _ = template_picture(img, './model/again.png', 0.75, 1)
    # print(stu)

    # cv2.imshow('Window Screenshot', img)
    # cv2.waitKey(0)
    # cv2.imwrite('again.png', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()

    # 冒险家名望
    # box = (277, 295, 35, 12)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # renown = calculateMovingSpeed(img)
    # print('冒险家名望:', renown)

    # 冒险家移速  (800x600)->box = (208, 467, 35, 11)   (1067x600)-> box = (342, 467, 35, 11)
    # box = (208, 467, 35, 11)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # speed = calculateMovingSpeed(img)
    # print('冒险家移速:', speed)

    # 冒险家PL值  (800x600)->box = (705, 467, 35, 11)   (1067x600)-> box = (839, 467, 35, 11)
    # box = (705, 590, 35, 8)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # pl = find_pl(img)
    # print('冒险家疲劳:', pl)

    # 冒险家职业名称
    # box = (126, 270, 265, 14)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('职业名称.jpg', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # career = call_api_direct(img)
    # print('冒险家职业:', extract_with_regex(career))

    # 冒险家等级、名称
    # box = (126, 256, 265, 12)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('冒险家等级、名称.jpg', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
    # api_response = call_api_direct(img)
    # level, name = split_level_and_name(api_response)
    # print(f"冒险家等级: {level}, 名称: {name}")  # 输出: 等级: 115, 名称: 疾风随欲

    # 冒险家技能
    # box = (300, 530, 215, 63)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # box = (299, 532, 217, 63)
    # img = image[box[1]:box[1] + box[3], box[0]:box[0] + box[2]]  # 使用裁剪区域裁剪图像
    # cv2.imwrite('skill.jpg', img)  # 请将'skill.jpg' 替换为想要保存的图片路径
    # start = time.perf_counter()
    # templates = load_templates("skills_output")
    # img = cv2.imread("skill.jpg", cv2.IMREAD_GRAYSCALE)
    # keys = detect_ready_skills(img, templates, visualize=True)
    # end = time.perf_counter()
    # print("未冷却技能：", keys)
    # print(f"耗时：{(end - start) * 1000:.2f} 毫秒")
    # selected_key = random.choice(keys)
    # print("随机选择的按键是:", selected_key)
    # extract_skill_slots("skill.jpg", visualize=False)
    # templates = load_templates("skills_output")
    #
    # start = time.perf_counter()
    # keys = detect_ready_skills("skill.jpg", templates, visualize=False)
    # end = time.perf_counter()
    # print("未冷却技能：", keys)
    # print(f"耗时：{(end - start) * 1000:.2f} 毫秒")
    # 显示截图
    # cv2.imshow('Window Screenshot', image)
    # cv2.waitKey(0)
    # # 保存图片
    # cv2.write('2.jpg', img)  # 请将'saved_image.jpg' 替换为想要保存的图片路径
    # cv2.destroyAllWindows()
